// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <zencore/uid.h>
#include <zenutil/basicfile.h>

namespace zen {

class CasLogFile
{
public:
	CasLogFile();
	~CasLogFile();

	enum class Mode
	{
		kRead,
		kWrite,
		kTruncate
	};

	static bool IsValid(std::filesystem::path FileName, size_t RecordSize);
	void		Open(std::filesystem::path FileName, size_t RecordSize, Mode Mode);
	void		Append(const void* DataPointer, uint64_t DataSize);
	void		Replay(std::function<void(const void*)>&& Handler, uint64_t SkipEntryCount);
	void		Flush();
	void		Close();
	uint64_t	GetLogSize();
	uint64_t	GetLogCount();

private:
	struct FileHeader
	{
		uint8_t	 Magic[16];
		uint32_t RecordSize = 0;
		Oid		 LogId;
		uint32_t ValidatedTail = 0;
		uint32_t Pad[6];
		uint32_t Checksum = 0;

		static const inline uint8_t MagicSequence[16] = {'.', '-', '=', ' ', 'C', 'A', 'S', 'L', 'O', 'G', 'v', '1', ' ', '=', '-', '.'};

		ZENCORE_API uint32_t ComputeChecksum();
		void				 Finalize() { Checksum = ComputeChecksum(); }
	};

	static_assert(sizeof(FileHeader) == 64);

private:
	void Open(std::filesystem::path FileName, size_t RecordSize, BasicFile::Mode Mode);

	BasicFile			  m_File;
	FileHeader			  m_Header;
	size_t				  m_RecordSize	 = 1;
	std::atomic<uint64_t> m_AppendOffset = 0;
};

template<typename T>
class TCasLogFile : public CasLogFile
{
public:
	static bool IsValid(std::filesystem::path FileName) { return CasLogFile::IsValid(FileName, sizeof(T)); }
	void		Open(std::filesystem::path FileName, Mode Mode) { CasLogFile::Open(FileName, sizeof(T), Mode); }

	// This should be called before the Replay() is called to do some basic sanity checking
	bool Initialize() { return true; }

	void Replay(Invocable<const T&> auto Handler, uint64_t SkipEntryCount)
	{
		CasLogFile::Replay(
			[&](const void* VoidPtr) {
				const T& Record = *reinterpret_cast<const T*>(VoidPtr);

				Handler(Record);
			},
			SkipEntryCount);
	}

	void Append(const T& Record)
	{
		// TODO: implement some more efficent path here so we don't end up with
		// a syscall per append

		CasLogFile::Append(&Record, sizeof Record);
	}

	void Append(const std::span<T>& Records) { CasLogFile::Append(Records.data(), sizeof(T) * Records.size()); }
};

}  // namespace zen
